#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>

#include "definition.h"
//#include "packetprocess.h"

#define SERV_IP "127.0.0.1"
#define SERV_PORT 6339
#define MAXNEVENTS 200
#define BUF_SIZE 1048576

#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))

void stopmain(int signal);
int server_fd, efd;

int main(int argc, char *argv[]) {
	// ./run [PORT] [IP address] [loop times] [len_msg] [num_msg] [x]
    struct sockaddr_in server_addr, client_addr;
    socklen_t len_cli = sizeof(struct sockaddr);

    struct epoll_event events[MAXNEVENTS], tmp_event;

    signal(SIGINT, stopmain);

    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        printf("socket failed ---------\n"); exit(-1);
    }

    uint16_t serv_port = SERV_PORT; // port
    if (argc >= 2) serv_port = atoi(argv[1]);

    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(serv_port);
    if (argc >= 3) server_addr.sin_addr.s_addr = inet_addr(argv[2]);
    else server_addr.sin_addr.s_addr = inet_addr(SERV_IP);

    if (connect(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) {
        printf("connect failed --------\n"); exit(-1);
    }

    fcntl(server_fd, F_SETFL, fcntl(server_fd, F_GETFL, 0) | O_NONBLOCK);

    if ((efd = epoll_create(MAXNEVENTS)) == -1) {
        printf("epoll_create failed ---\n"); exit(-1);
    }

    tmp_event.events = EPOLLOUT | EPOLLET;
    tmp_event.data.fd = server_fd;

    epoll_ctl(efd, EPOLL_CTL_ADD, server_fd, &tmp_event);

    int num_epwait, tmpi, tempfd;

    char *buff;
    //memset(buff, 'A', 2048);
    int len_msg = 100;
    if (argc >= 5) {
        len_msg = atoi(argv[4]);
    }
    if (len_msg <= 10)
        len_msg = 10;
	if(len_msg > 65536)
		len_msg = 65536;
    //buff[len_msg - 1] = '\0';

    int num_msg = 1;
    if (argc >= 6) num_msg = atoi(argv[5]);
    if (num_msg < 1) num_msg = 1;
    //buff = (char *)malloc(len_msg)
    if (num_msg > 1) {
        buff = (char *)malloc(len_msg * num_msg + sizeof(TCPheader) * (num_msg + 1));
    }
    else buff = (char *)malloc(len_msg + sizeof(TCPheader));
    int len_all = create_sample_serpacket(num_msg, buff, len_msg);

    char buffread[BUF_SIZE];

    int loop = 0;
    if (argc >= 4) loop = atoi(argv[3]);
    if (loop <= 0) loop = 5;

    int read_times = 0, write_times = 0, percent = 1;
    int read_total = 0, write_total = 0;
    int len_send = 0, len_recv = 0;

	struct timeval curtime; // for time pin
	uint32_t recvtime;
	uint32_t sendtime;
	char filenamefortime[60];
	int tmppin = 0; // count number
	uint32_t pintimegap[10000];
	memset(pintimegap, 0, 10000 * sizeof(uint32_t));
	if (argc >= 7)
		sprintf(filenamefortime, "./trace/cli_pin_%d_%s.txt", serv_port, argv[6]);
	else
		sprintf(filenamefortime, "./trace/cli_pin_%d.txt", serv_port);

    printf("#### begin while loop\n");
    while (read_times < loop || write_times < loop) {
        //printf("#### in while loop\n");
        num_epwait = epoll_wait(efd, events, MAXNEVENTS, -1);
        for (tmpi = 0; tmpi < num_epwait; tmpi++) { // for every new event

            if (events[tmpi].events & EPOLLOUT) { // data out
                //printf("EPOLLOUT ----------\n");
                tempfd = events[tmpi].data.fd;
                if (tempfd < 0) {
                    printf("Why sockfd is 0??????????????????????????????\n"); continue;
                }
                int len_out = len_all - len_send;
                int ret;

                while (len_out > 0) {
					ret = write(tempfd, buff + len_send, (len_out > 4096) ? 4096 : len_out);
                    if (ret > 0) {
                        len_out -= ret; // write
                        len_send += ret;
                    }
                    else if (errno == EAGAIN) { //ret == -1 && 
                        printf("[send]Write buffer full.\n");
                        break;
                    }
                    else if (errno != EAGAIN) { //ret == -1 && 
                        printf("[send]Remote connection closed, so we close too......\n");
                        close(tempfd);
                        goto FINISHSH;
                    }
                }

                //printf("####[send]ret: %d, errno: %d, len out: %d, len send: %d\n", ret, errno, len_out, len_send); /////////////

                if (len_send == len_all) { //finish send
					gettimeofday(&curtime, NULL); //// time
					sendtime = curtime.tv_sec * 1000000 + curtime.tv_usec;

                    tmp_event.events = EPOLLIN | EPOLLET;
                    tmp_event.data.fd = tempfd;
                    epoll_ctl(efd, EPOLL_CTL_MOD, tempfd, &tmp_event);

                    write_total += len_send;
                    len_send = 0;

                    //printf("write: %d times\n", write_times);
                    write_times++;
                }
                else {//buffer full
                    printf("%d bytes to write.\n", len_all - len_send);
                }
            }

            else if (events[tmpi].events & EPOLLIN) { // data in
                //printf("EPOLLIN -----------\n");
                tempfd = events[tmpi].data.fd;
                if (tempfd < 0) {
                    printf("Why sockfd is 0??????????????????????????????\n");
                    continue;
                }
                //*
                int ret, len_in = len_all - len_recv;

                while ((ret = read(tempfd, buffread, MIN(BUF_SIZE, len_in) )) > 0) {
                //while ((ret = read(tempfd, buffread, BUF_SIZE)) > 0) {
                    len_recv += ret;
                    len_in -= ret;
                    if (len_in == 0) {
                        break;
                    }
                }
                //printf("recvdata: %s\n", buffread + sizeof(TCPheader));
                //printf("####[read]ret: %d, errno: %d, ret: %d, recv len: %d\n", ret, errno, ret, len_recv);
                if (ret == -1 && errno == EAGAIN) {  
                    //printf("[recv]Buffer empty.\n");
                }
                else if (ret == 0 && errno != 0) {
                    printf("[recv]Remote connection closed, so we close too......\n");
                    close(tempfd);
                    goto FINISHSH;
                }
                else if (ret < 0) {
                    printf("[recv]Read error, errno: %d.\n", errno);
                    close(tempfd);
                    goto FINISHSH;
                }


                if (len_recv == len_all) { //finish recv
					gettimeofday(&curtime, NULL); //// time
					recvtime = curtime.tv_sec * 1000000 + curtime.tv_usec;

					if (recvtime > sendtime) {
						pintimegap[tmppin] = recvtime - sendtime;
						tmppin = (tmppin + 1) % 10000;
					}

                    tmp_event.events = EPOLLOUT | EPOLLET;
                    tmp_event.data.fd = tempfd;
                    epoll_ctl(efd, EPOLL_CTL_MOD, tempfd, &tmp_event);

                    read_total += len_recv;
                    len_recv = 0;

                    //printf("read: %d times\n", read_times);
                    read_times++;
                }
                else {//buffer empty
                    //printf("%d bytes to read.\n", len_all - len_recv);
                }
                //*/
            }
        } // finished for-loop

        if(write_times * 10 >= percent * loop) {
            printf("%d0\%\n", percent++);
        }
        //printf("@@@@ end of while-loop................\n");
        // if (--loop < 0) break;
    } // finished while-loop
    printf("read_total: %d write_total: %d\n", read_total, write_total);

FINISHSH:
    close(server_fd);
    close(efd);
	FILE *ouffortime = fopen(filenamefortime, "w");
	for (tmpi = 0; tmpi < 10000; tmpi++) {
		fprintf(ouffortime, "%lu\n", pintimegap[tmpi]);
	}
	fclose(ouffortime);
    printf("#### end while loop, close\n");
    return 0;
}

void stopmain(int signal) {
    printf("**** Ctrl+C ****\n");
    close(server_fd);
    close(efd);
    printf("**** End Ctrl+C \n");
    _exit(0);
}

